/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.util.*;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystemCapability;
import java.lang.ref.*;
/** Registraction list of all data objects in the system.
* Maps data objects to its handlers.
*
* @author Jaroslav Tulach
*/
final class DataObjectPool extends Object
implements javax.swing.event.ChangeListener {
/** hashtable that maps FileObject to DataObjectPool.Item
* @associates Item*/
private HashMap map = new HashMap ();
/** the pool for all objects */
static final DataObjectPool POOL = new DataObjectPool ();
static {
org.openide.TopManager.getDefault ().getLoaderPool ().addChangeListener (
POOL
);
}
static boolean debug (FileObject fo) {
return fo.getParent () != null && fo.getParent ().isRoot ();
}
/** validator */
private static final Validator VALIDATOR = new Validator ();
/** Checks whether there is a data object with primary file
* passed thru the parameter.
*
* @param fo the file to check
* @return data object with fo as primary file or null
*/
public synchronized DataObject find (FileObject fo) {
Item doh = (Item)map.get (fo);
return doh == null ? null : doh.getDataObjectOrNull ();
}
/** Refresh of all folders.
*/
private void refreshAllFolders () {
Set files;
synchronized (this) {
files = new HashSet (map.keySet ());
}
Iterator it = files.iterator ();
while (it.hasNext ()) {
FileObject fo = (FileObject)it.next ();
if (fo.isFolder ()) {
DataObject obj = find (fo);
if (obj instanceof DataFolder) {
DataFolder df = (DataFolder)obj;
df.refresh ();
}
}
}
}
/** Rescans all fileobjects in given set.
* @param s set of FileObjects
* @return set of DataObjects that refused to be revalidated
*/
public Set revalidate (Set s) {
return VALIDATOR.revalidate (s);
}
/** Registers new DataObject instance.
* @param fo primary file for obj
* @param loader the loader of the object to be created
*
* @return object with common information for this <CODE>DataObject</CODE>
* @exception DataObjectExistsException if the file object is already registered
*/
public synchronized Item register (FileObject fo, DataLoader loader)
throws DataObjectExistsException {
Item doh = (Item)map.get (fo);
// if Item for this file has not been created yet
if (doh == null) {
doh = new Item (fo);
map.put (fo, doh);
VALIDATOR.notifyRegistered (fo);
return doh;
} else {
DataObject obj = doh.getDataObjectOrNull ();
if (obj == null || VALIDATOR.reregister (obj, loader)) {
// the item is to be finalize => create new
doh = new Item (fo);
map.put (fo, doh);
return doh;
}
// PENDING only for debug
// System.err.println ("Original stack (" + doh.getDataObject () + "): ");
// System.err.println (doh.toString ());
// Thread.dumpStack();
// throw exception with the existing data object
final String s = doh.toString ();
throw new DataObjectExistsException (obj) {
public String getMessage () {
return s;
}
};
}
}
/** Deregister.
* @param item the item with common information to deregister
* @param refresh true if the parent folder should be refreshed
*/
private synchronized void deregister (Item item, boolean refresh) {
FileObject fo = item.primaryFile;
Item previous = (Item)map.remove (fo);
if (previous != null && previous != item) {
// ops, mistake,
// return back the original
map.put (fo, previous);
return;
}
// refresh of parent folder
if (refresh) {
fo = fo.getParent ();
if (fo != null) {
Item item2 = (Item)map.get (fo);
if (item2 != null) {
DataFolder df = (DataFolder) item2.getDataObjectOrNull();
if (df != null) {
VALIDATOR.refreshFolderOf (df);
}
}
}
}
}
/** Changes the primary file to new one.
* @param item the item to change
* @param newFile new primary file to set
*/
private synchronized void changePrimaryFile (
Item item, FileObject newFile
) {
map.remove (item.primaryFile);
item.primaryFile = newFile;
map.put (newFile, item);
}
/** When the loader pool is changed, then all objects are rescanned.
*/
public void stateChanged (javax.swing.event.ChangeEvent ev) {
HashSet set;
synchronized (this) {
set = new HashSet (map.keySet ());
}
revalidate (set);
}
/** One item in object pool.
*/
static final class Item extends Object {
/** primary file */
FileObject primaryFile;
/** weak reference data object with this primary file */
private Reference obj;
// [PENDING] hack to check the stack when the DataObject has been created
// private Exception stack;
/** @param fo primary file
* @param pool object pool
*/
public Item (FileObject fo) {
this.primaryFile = fo;
// [PENDING] // stores stack
/* java.io.StringWriter sw = new java.io.StringWriter ();
stack = new Exception ();
}
// [PENDING] toString returns original stack
public String toString () {
return stack.toString ();*/
}
/** Setter for the data object. Called immediatelly as possible.
* @param obj the data object for this item
*/
public void setDataObject (DataObject obj) {
this.obj = new WeakReference (obj);
}
/** Getter for the data object.
* @return the data object or null
*/
DataObject getDataObjectOrNull () {
return (DataObject)this.obj.get ();
}
/** Getter for the data object.
* @return the data object
* @exception IllegalStateException if the data object has been lost
* due to weak references (should not happen)
*/
public DataObject getDataObject () {
DataObject obj = getDataObjectOrNull ();
if (obj == null) {
throw new IllegalStateException ();
}
return obj;
}
/** Deregister one reference.
* @param refresh true if the parent folder should be refreshed
*/
public void deregister (boolean refresh) {
POOL.deregister (this, refresh);
}
/** Changes the primary file to new one.
* @param newFile new primary file to set
*/
public void changePrimaryFile (FileObject newFile) {
POOL.changePrimaryFile (this, newFile);
}
/** Is the item valid?
*/
public boolean isValid () {
return POOL.map.containsKey (primaryFile);
}
public String toString () {
DataObject obj = (DataObject)this.obj.get ();
if (obj == null) {
return " nothing"; // NOI18N
}
return obj.toString ();
}
}
/** Validator to allow rescan of files.
*/
private static final class Validator extends Object
implements DataLoader.RecognizedFiles {
/** set of all files that should be revalidated (FileObject) */
private Set files;
/** current thread that is in the validator */
private Thread current;
/** number of threads waiting to enter the validation */
private int waiters;
/** set of files that has been marked recognized (FileObject)
* @associates FileObject*/
private HashSet recognizedFiles;
/** set with all objects that refused to be discarded (DataObject)
* @associates DataObject*/
private HashSet refusingObjects;
/** set of files that has been registered during revalidation
* @associates FileObject*/
private HashSet createdFiles;
/** Enters the section.
* @param set set of files that should be processed
* @return the set of files concatenated with any previous sets
*/
private synchronized Set enter (Set set) {
waiters++;
while (current != null) {
try {
wait ();
} catch (InterruptedException ex) {
}
}
current = Thread.currentThread ();
waiters--;
if (files == null) {
files = set;
} else {
files.addAll (set);
}
return files;
}
/** Leaves the critical section.
*/
private synchronized void exit () {
current = null;
if (waiters == 0) {
files = null;
}
notify ();
}
/** If there is another waiting thread, then I can
* cancel my computation.
*/
private synchronized boolean goOn () {
return waiters == 0;
}
/** Called to either refresh folder, or register the folder to be
* refreshed later is validation is in progress.
*/
public void refreshFolderOf (DataFolder df) {
if (createdFiles == null) {
// no validator in progress
df.refresh ();
}
}
/** Mark this file as being recognized. It will be excluded
* from further processing.
*
* @param fo file object to exclude
*/
public void markRecognized (FileObject fo) {
recognizedFiles.add (fo);
}
public void notifyRegistered (FileObject fo) {
if (createdFiles != null) {
createdFiles.add (fo);
}
}
/** Reregister new object for already existing file object.
* @param obj old object existing
* @param loader loader of new object to create
* @return true if the old object has been discarded and new one can
* be created
*/
public boolean reregister (DataObject obj, DataLoader loader) {
if (recognizedFiles == null) {
// revalidation not in progress
return false;
}
if (obj.getLoader () == loader) {
// no change in loader =>
return false;
}
if (createdFiles.contains (obj.getPrimaryFile ())) {
// if the file already has been created
return false;
}
if (refusingObjects.contains (obj)) {
// the object has been refused before
return false;
}
try {
obj.setValid (false);
return true;
} catch (java.beans.PropertyVetoException ex) {
refusingObjects.add (obj);
return false;
}
}
/** Rescans all fileobjects in given set.
* @param s set of FileObjects
* @return set of objects that refused to be revalidated
*/
public Set revalidate (Set s) {
// holds all created object, so they are not garbage
// collected till this method ends
LinkedList createObjects = new LinkedList ();
try {
s = enter (s);
recognizedFiles = new HashSet ();
refusingObjects = new HashSet ();
createdFiles = new HashSet ();
DataLoaderPool pool = org.openide.TopManager.getDefault ().getLoaderPool ();
Iterator it = s.iterator ();
while (it.hasNext () && goOn ()) {
FileObject fo = (FileObject)it.next ();
if (!recognizedFiles.contains (fo)) {
try {
// findDataObject
// is not using method DataObjectPool.find to locate data object
// directly for primary file, that is good
DataObject obj = pool.findDataObject (fo, this);
createObjects.add (obj);
// the previous data object should be canceled
DataObject orig = POOL.find (fo);
if (obj != orig && orig != null) {
try {
orig.setValid (false);
} catch (java.beans.PropertyVetoException ex) {
refusingObjects.add (orig);
}
}
} catch (java.io.IOException ex) {
}
}
}
return refusingObjects;
} finally {
recognizedFiles = null;
refusingObjects = null;
createdFiles = null;
exit ();
POOL.refreshAllFolders ();
}
}
}
}
/*
* Log
* 16 Gandalf 1.15 1/16/00 Jaroslav Tulach I18N
* 15 Gandalf 1.14 1/16/00 Jaroslav Tulach TemplatesExplorer
* removed, startup faster
* 14 Gandalf 1.13 1/12/00 Ian Formanek NOI18N
* 13 Gandalf 1.12 11/23/99 Jaroslav Tulach WeakReferences instead of
* Soft ones.
* 12 Gandalf 1.11 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 11 Gandalf 1.10 10/5/99 Jaroslav Tulach Synchronization
* improvement.
* 10 Gandalf 1.9 9/30/99 Jaroslav Tulach OpenSupport is attached
* to setValid veto change of its data object.
* 9 Gandalf 1.8 9/30/99 Jaroslav Tulach DataLoader is now
* serializable.
* 8 Gandalf 1.7 9/28/99 Jaroslav Tulach Changes in loader pool
* are reflected in repository.
* 7 Gandalf 1.6 7/23/99 Petr Hamernik dispose() bugfix
* 6 Gandalf 1.5 7/20/99 Jaroslav Tulach Speed up of
* DataObject.find
* 5 Gandalf 1.4 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 4 Gandalf 1.3 4/22/99 Jaroslav Tulach Better synch with
* finalization.
* 3 Gandalf 1.2 4/19/99 Jan Jancura To remember parsed
* informations
* 2 Gandalf 1.1 1/17/99 Jaroslav Tulach isValid test
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
*/